package com.sojson.util.log;

import java.io.PrintStream;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import com.sojson.constant.Constant;
import com.sojson.constant.ConstantByMap;
import com.sojson.constant.IpConfig;
import com.sojson.util.StringUtil;
import com.sojson.util.id.IdUtil;
import com.sojson.util.json.JsonUtil;
import com.sojson.util.log.enums.ELogLevel;
import com.sojson.util.log.enums.ELogPackage;
import com.sojson.util.param.ParamUtil;
import com.sojson.util.servlet.ServletUtil;
import com.sojson.util.token.TokenUtil;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.filter.LevelFilter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP;
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
import ch.qos.logback.core.spi.FilterReply;
import ch.qos.logback.core.util.FileSize;
import ch.qos.logback.core.util.OptionHelper;
import lombok.extern.slf4j.Slf4j;

/**
 * 日志工具类
 *
 * @author liu
 * @date 2021-09-03
 */
@Slf4j
public class LogUtil {

    public static final String TRACE_ID_FIELD = "traceId";
    private static String consoleAppenderName = "serve-console";
    private static String maxFileSize = "50MB";
    private static String totalSizeCap = "10GB";
    private static int maxHistory = 30;
    private static ConsoleAppender defaultConsoleAppender = null;

    /** 日志上下文 */
    private static final LoggerContext LOGGER_CONTEXT = (LoggerContext)LoggerFactory.getILoggerFactory();

    /**
     * 创建日志ID
     */
    public static String creTraceId() {
        return IdUtil.uuid();
    }

    /**
     * 设置日志ID
     */
    public static void setTraceId() {
        setTraceId(creTraceId());
    }

    /**
     * 设置日志ID
     *
     * @param traceId
     */
    public static void setTraceId(String traceId) {
        MDC.put(TRACE_ID_FIELD, traceId);
    }

    /**
     * 获取日志ID
     *
     * @return
     */
    public static String getTraceId() {
        return MDC.get(TRACE_ID_FIELD);
    }

    /**
     * 清空日志ID
     *
     * @param traceId
     */
    public static void clearTraceId() {
        MDC.clear();
    }

    /**
     * 修改日志等级
     *
     * @param pkg   日志包
     * @param level 日志等级
     */
    public static void update(ELogPackage pkg, ELogLevel level) {
        update(pkg.getCode(), level);
    }

    /**
     * 修改日志等级
     *
     * @param pkg   日志包
     * @param level 日志等级
     */
    public static void update(String pkg, ELogLevel level) {
        LOGGER_CONTEXT.getLogger(pkg).setLevel(Level.valueOf(level.getCode()));
    }

    /**
     * 修改日志等级
     *
     * @param cla   日志类
     * @param level 日志等级
     */
    public static void update(Class<?> cla, ELogLevel level) {
        LOGGER_CONTEXT.getLogger(cla).setLevel(Level.valueOf(level.getCode()));
    }

    /**
     * 添加Logback的Appender
     *
     * @param cla   日志类
     * @param level 日志等级
     */
    public static void addAppender(Class<?> cla, ELogLevel level) {
        LOGGER_CONTEXT.getLogger(cla).setLevel(Level.valueOf(level.getCode()));
    }

    /**
     * 修改Logback的Appender
     *
     * @param cla   日志类
     * @param level 日志等级
     */
    public static void updateAppender(Class<?> cla, ELogLevel level) {
        ch.qos.logback.classic.Logger logger = LOGGER_CONTEXT.getLogger(cla);
        Appender<ILoggingEvent> appender = logger.getAppender(null);
    }

    /**
     * 删除Logback的Appender
     *
     * @param pkg   日志包
     * @param level 日志等级
     */
    public static void removeAppender(ELogPackage pkg, String appender) {
        removeAppender(pkg.getCode(), appender);
    }

    /**
     * 删除Logback的Appender
     *
     * @param pkg   日志包
     * @param level 日志等级
     */
    public static void removeAppender(String pkg, String appender) {
        LOGGER_CONTEXT.getLogger(pkg).detachAppender(appender);
    }

    /**
     * 删除Logback的Appender
     *
     * @param cla   日志类
     * @param level 日志等级
     */
    public static void removeAppender(Class<?> cla, String appender) {
        LOGGER_CONTEXT.getLogger(cla).detachAppender(appender);
    }

    /** 创建Logback的控制台的Appender */
    public static ConsoleAppender<ILoggingEvent> createConsoleAppender(String name) {
        ConsoleAppender<ILoggingEvent> appender = new ConsoleAppender<>();
        appender.setContext(LOGGER_CONTEXT);
        appender.setName(name);
        appender.addFilter(createLevelFilter(Level.DEBUG));
        appender.setEncoder(createEncoder());
        appender.start();
        return appender;
    }

    /**
    * description: 创建日志文件的file appender
    *
    * @param name
    * @param level
    * @return ch.qos.logback.core.rolling.RollingFileAppender
    * @author Hlingoes 2020/6/10
    */
    public static RollingFileAppender<ILoggingEvent> createAppender(String name, Level level) {
        RollingFileAppender<ILoggingEvent> appender = new RollingFileAppender<>();
        // 这里设置级别过滤器
        appender.addFilter(createLevelFilter(level));
        // 设置上下文，每个logger都关联到logger上下文，默认上下文名称为default。
        // 但可以使用<scope="context">设置成其他名字，用于区分不同应用程序的记录。一旦设置，不能修改。
        appender.setContext(LOGGER_CONTEXT);
        // appender的name属性
        appender.setName(name.toUpperCase() + "-" + level.levelStr.toUpperCase());
        // 读取logback配置文件中的属性值，设置文件名
        String logPath =
            OptionHelper.substVars("${logPath}-" + name + "-" + level.levelStr.toLowerCase() + ".log", LOGGER_CONTEXT);
        appender.setFile(logPath);
        appender.setAppend(true);
        appender.setPrudent(false);
        // 加入下面两个节点
        appender.setRollingPolicy(createRollingPolicy(name, level, LOGGER_CONTEXT, appender));
        appender.setEncoder(createEncoder());
        appender.start();
        return appender;
    }

    /** 创建日志的打印级别 */
    private static Filter<ILoggingEvent> createLevelFilter(Level level) {
        LevelFilter levelFilter = new LevelFilter();
        levelFilter.setLevel(level);
        levelFilter.setOnMatch(FilterReply.ACCEPT);
        levelFilter.setOnMismatch(FilterReply.DENY);
        levelFilter.start();
        return levelFilter;
    }

    /** 创建日志的字符集 */
    private static PatternLayoutEncoder createEncoder() {
        PatternLayoutEncoder encoder = new PatternLayoutEncoder();
        // 设置上下文,每个logger都关联到logger上下文,默认上下文名称为default
        // 但可以使用<scope="context">设置成其他名字,用于区分不同应用程序的记录。一旦设置,不能修改。
        encoder.setContext(LOGGER_CONTEXT);
        // 设置格式
        String pattern = OptionHelper.substVars("${pattern}", LOGGER_CONTEXT);
        encoder.setPattern(pattern);
        encoder.setCharset(Charset.forName(Constant.DEFAULT_ENCODING));
        encoder.start();
        return encoder;
    }

    /**
    * description: 设置日志的滚动策略
    *
    * @param name
    * @param level
    * @param context
    * @param appender
    * @return ch.qos.logback.core.rolling.TimeBasedRollingPolicy
    * @author Hlingoes 2020/6/10
    */
    private static TimeBasedRollingPolicy<ILoggingEvent> createRollingPolicy(String name, Level level,
        LoggerContext context, FileAppender<ILoggingEvent> appender) {
        // 读取logback配置文件中的属性值，设置文件名
        String fp = OptionHelper.substVars(
            "${logPath}/${LOG_NAME_PREFIX}-" + name + "-" + level.levelStr.toLowerCase() + "_%d{yyyy-MM-dd}_%i.log",
            context);
        TimeBasedRollingPolicy<ILoggingEvent> rollingPolicyBase = new TimeBasedRollingPolicy<>();
        // 设置上下文，每个logger都关联到logger上下文，默认上下文名称为default。
        // 但可以使用<scope="context">设置成其他名字，用于区分不同应用程序的记录。一旦设置，不能修改。
        rollingPolicyBase.setContext(context);
        // 设置父节点是appender
        rollingPolicyBase.setParent(appender);
        // 设置文件名模式
        rollingPolicyBase.setFileNamePattern(fp);
        SizeAndTimeBasedFNATP<ILoggingEvent> sizeAndTimeBasedFNATP = new SizeAndTimeBasedFNATP<>();
        // 最大日志文件大小
        sizeAndTimeBasedFNATP.setMaxFileSize(FileSize.valueOf(maxFileSize));
        rollingPolicyBase.setTimeBasedFileNamingAndTriggeringPolicy(sizeAndTimeBasedFNATP);
        // 设置最大历史记录为30条
        rollingPolicyBase.setMaxHistory(maxHistory);
        // 总大小限制
        rollingPolicyBase.setTotalSizeCap(FileSize.valueOf(totalSizeCap));
        rollingPolicyBase.start();

        return rollingPolicyBase;
    }

    /** 获取Logback中所有Appender */
    public static Map<String, Appender<ILoggingEvent>> allAppenders() {
        Map<String, Appender<ILoggingEvent>> appenderMap = new HashMap<>();
        LoggerContext context = (LoggerContext)LoggerFactory.getILoggerFactory();
        for (ch.qos.logback.classic.Logger logger : context.getLoggerList()) {
            for (Iterator<Appender<ILoggingEvent>> index = logger.iteratorForAppenders(); index.hasNext();) {
                Appender<ILoggingEvent> appender = index.next();
                appenderMap.put(appender.getName(), appender);
            }
        }
        return appenderMap;
    }

    /**
     * 将日志打印到控制台
     *
     * @param log
     * @param request
     * @throws Exception
     */
    public static void outConsole(Logger log) throws Exception {
        outConsole(log, ServletUtil.getHttpServletRequest());
    }

    /**
     * 将日志打印到控制台
     *
     * @param log
     * @param request
     * @throws Exception
     */
    public static void outConsole(Logger log, HttpServletRequest request) throws Exception {
        log.info("=======================================================");
        log.info("=======================================================");
        log.info("=======================================================");
        // 输出访问的项目
        log.info("访问项目: {}", IpConfig.getUrl());
        // 输出访问的路径
        log.info("访问路径: {}", ServletUtil.getRequestMapping(request));
        String errMapping = ServletUtil.getErrMapping(request);
        if (StringUtil.isNotBlankString(errMapping)) {
            log.info("访问路径: {}", errMapping);
        }
        // 输出来源IP
        log.info("来源IP : {}", ServletUtil.getTargetIp(request));
        // 输出用户
        if (ParamUtil.isLogin()) {
            log.info("用户ID : {}", TokenUtil.getUserId());
            log.info("用户角色: {}", TokenUtil.getRoleNames());
        } else {
            log.info("用户ID : 用户未登录");
        }
        // Token
        log.info("Token : {}", ServletUtil.getReqHeader(ConstantByMap.TOKEN_NAME));

        // 输入流的参数
        String paramBody = ServletUtil.getParamBody();
        log.info("=======");
        if (StringUtil.isNotBlankString(paramBody)) {
            try {
                log.info("body: " + StringUtil.ENTER + "{}", JsonUtil.format(paramBody));
            } catch (Exception e) {
                log.info("body: {}", paramBody);
            }
        } else {
            log.info("body: {}");
        }

        // 普通参数
        Map<String, String[]> parameterMap = ServletUtil.getParameterMap();
        log.info("=======");
        if (StringUtil.isNotBlankObject(parameterMap) && parameterMap.size() > 0) {
            Set<Entry<String, String[]>> entrySet = parameterMap.entrySet();
            for (Entry<String, String[]> entry : entrySet) {
                Object val = null;
                String[] value = entry.getValue();
                if (StringUtil.isNotBlankObject(value)) {
                    int valLen = value.length;
                    if (valLen == 0) {
                        val = "";
                    } else if (valLen == 1) {
                        val = value[0];
                    } else {
                        val = value;
                    }
                }
                log.info("{}: {}", entry.getKey(), val);
            }
        }
    }

    public static void setOut() {
        PrintStream myStream = new PrintStream(System.out) {
            @Override
            @SuppressWarnings("synthetic-access")
            public void println(String msg) {
                log.info(msg);
            }
        };
        System.setOut(myStream);
    }

}